<?php
// +-------------------------------------------------------------------------------
// | DingZone [ A Rapid Development Framework For Dingtalk ]
// +-------------------------------------------------------------------------------
// | 钉钉加解密函数 v1.0
// |
// | Copyright (c) 2014-2018 http://www.heimalanshi.com All rights reserved.
// | Author : Jie
// | Date : 2018-08-09
// +-------------------------------------------------------------------------------

namespace ding\util;

use think\Log;

class DingtalkCrypt {
    private $m_token;
    private $m_encodingAesKey;
    private $m_corpid;

    public function __construct($token, $encodingAesKey, $corpid) {
        $this->m_token = $token;
        $this->m_encodingAesKey = $encodingAesKey;
        $this->m_corpid = $corpid;
    }

    /**
     * 加密消息
     *
     * @param $plain
     * @param $timeStamp
     * @param $nonce
     * @param $encryptMsg
     * @return int|mixed
     */
    public function encryptMsg($plain, $timeStamp, $nonce, &$encryptMsg) {
        $pc = new Prpcrypt($this->m_encodingAesKey);
        $array = $pc->encrypt($plain, $this->m_corpid);
        $ret = $array[0];
        if ($ret != 0) {
            return $ret;
        }
        if ($timeStamp == null) {
            $timeStamp = time();
        }
        $encrypt = $array[1];
        $array = $this::getSHA1($this->m_token, $timeStamp, $nonce, $encrypt);
        $ret = $array[0];
        if ($ret != 0) {
            return $ret;
        }
        $signature = $array[1];
        $encryptMsg = json_encode(array(
            "msg_signature" => $signature,
            "encrypt" => $encrypt,
            "timeStamp" => $timeStamp,
            "nonce" => $nonce
        ));
        return ErrorCode::$OK;
    }

    /**
     * 解密消息
     *
     * @param $signature
     * @param $timeStamp
     * @param $nonce
     * @param $encrypt
     * @param $decryptMsg
     * @return int|mixed
     */
    public function decryptMsg($signature, $timeStamp, $nonce, $encrypt, &$decryptMsg) {
        if (strlen($this->m_encodingAesKey) != 43) {
            return ErrorCode::$IllegalAesKey;
        }
        $pc = new Prpcrypt($this->m_encodingAesKey);
        if ($timeStamp == null) {
            $timeStamp = time();
        }
        $array = $this::getSHA1($this->m_token, $timeStamp, $nonce, $encrypt);
        $ret = $array[0];
        if ($ret != 0) {
            return $ret;
        }
        $verifySignature = $array[1];
        if ($verifySignature != $signature) {
            return ErrorCode::$ValidateSignatureError;
        }
        $result = $pc->decrypt($encrypt, $this->m_corpid);
        if ($result[0] != 0) {
            return $result[0];
        }
        $decryptMsg = $result[1];
        return ErrorCode::$OK;
    }

    /**
     * 计算sha1值
     *
     * @param $token
     * @param $timestamp
     * @param $nonce
     * @param $encrypt_msg
     * @return array
     */
    private static function getSHA1($token, $timestamp, $nonce, $encrypt_msg) {
        try {
            $array = array($encrypt_msg, $token, $timestamp, $nonce);
            sort($array, SORT_STRING);
            $str = implode($array);
            return array(ErrorCode::$OK, sha1($str));
        } catch (\Exception $e) {
            Log::error($e->getMessage() . "\n" . $e->getTraceAsString());
            return array(ErrorCode::$ComputeSignatureError, null);
        }
    }

}

class ErrorCode {
    public static $OK = 0;

    /**
     * encodingAesKey 非法
     * @var int
     */
    public static $IllegalAesKey = 900004;
    /**
     * 签名验证错误
     * @var int
     */
    public static $ValidateSignatureError = 900005;
    /**
     * sha加密生成签名失败
     * @var int
     */
    public static $ComputeSignatureError = 900006;
    /**
     * aes 加密失败
     * @var int
     */
    public static $EncryptAESError = 900007;
    /**
     * aes 解密失败
     * @var int
     */
    public static $DecryptAESError = 900008;
    /**
     * suiteKey 校验错误
     * @var int
     */
    public static $ValidateSuiteKeyError = 900010;
}

class PKCS7Encoder {
    public static $block_size = 32;

    function encode($text) {
        $text_length = strlen($text);
        $amount_to_pad = PKCS7Encoder::$block_size - ($text_length % PKCS7Encoder::$block_size);
        if ($amount_to_pad == 0) {
            $amount_to_pad = PKCS7Encoder::$block_size;
        }
        $pad_chr = chr($amount_to_pad);
        $tmp = "";
        for ($index = 0; $index < $amount_to_pad; $index++) {
            $tmp .= $pad_chr;
        }
        return $text . $tmp;
    }

    function decode($text) {
        $pad = ord(substr($text, -1));
        if ($pad < 1 || $pad > PKCS7Encoder::$block_size) {
            $pad = 0;
        }
        return substr($text, 0, (strlen($text) - $pad));
    }
}

class Prpcrypt {
    public $key;

    function __construct($k) {
        $this->key = base64_decode($k . "=");
    }

    public function encrypt($text, $corpid) {
        try {
            //获得16位随机字符串，填充到明文之前
            $random = $this->getRandomStr();
            $text = $random . pack("N", strlen($text)) . $text . $corpid;
            // 网络字节序
            $size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
            $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
            $iv = substr($this->key, 0, 16);
            //使用自定义的填充方式对明文进行补位填充
            $pkc_encoder = new PKCS7Encoder;
            $text = $pkc_encoder->encode($text);
            mcrypt_generic_init($module, $this->key, $iv);
            //加密
            $encrypted = mcrypt_generic($module, $text);
            mcrypt_generic_deinit($module);
            mcrypt_module_close($module);
            //print(base64_encode($encrypted));
            //使用BASE64对加密后的字符串进行编码
            return array(ErrorCode::$OK, base64_encode($encrypted));
        } catch (\Exception $e) {
            Log::error($e->getMessage() . "\n" . $e->getTraceAsString());
            return array(ErrorCode::$EncryptAESError, null);
        }
    }

    public function decrypt($encrypted, $corpid) {
        try {
            $ciphertext_dec = base64_decode($encrypted);
            $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
            $iv = substr($this->key, 0, 16);
            mcrypt_generic_init($module, $this->key, $iv);
            $decrypted = mdecrypt_generic($module, $ciphertext_dec);
            mcrypt_generic_deinit($module);
            mcrypt_module_close($module);
        } catch (\Exception $e) {
            Log::error($e->getMessage() . "\n" . $e->getTraceAsString());
            return array(ErrorCode::$DecryptAESError, null);
        }
        try {
            //去除补位字符
            $pkc_encoder = new PKCS7Encoder;
            $result = $pkc_encoder->decode($decrypted);
            //去除16位随机字符串,网络字节序和AppId
            if (strlen($result) < 16)
                return "";
            $content = substr($result, 16, strlen($result));
            $len_list = unpack("N", substr($content, 0, 4));
            $xml_len = $len_list[1];
            $xml_content = substr($content, 4, $xml_len);
            $from_corpid = substr($content, $xml_len + 4);
        } catch (\Exception $e) {
            Log::error($e->getMessage() . "\n" . $e->getTraceAsString());
            return array(ErrorCode::$DecryptAESError, null);
        }
        if ($from_corpid != $corpid) {
            return array(ErrorCode::$ValidateSuiteKeyError, null);
        }
        return array(0, $xml_content);
    }

    function getRandomStr() {
        $str = "";
        $str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
        $max = strlen($str_pol) - 1;
        for ($i = 0; $i < 16; $i++) {
            $str .= $str_pol[mt_rand(0, $max)];
        }
        return $str;
    }
}